
import unittest
import sys

# **************************************** begin test sys.modules
module_fullname = "helper.person" 
# when run in WingIDE, it seems that it also use the same python to run multiple times
# so below codes may fail. But if you run this script separately, it always succeeds.
# assert module_fullname not in sys.modules

import helper.team as team
assert module_fullname in sys.modules
moduleid = id(sys.modules[module_fullname])

import helper.person
assert id(sys.modules[module_fullname]) == moduleid,"same module, not reload"
# **************************************** end test sys.modules

import helper.importee as importee
# from xxx import zzz, then zzz are local names reference the same object in that imported module
from helper.importee import immutable_variable
from helper.importee import mutable_variable

class TestImport(unittest.TestCase):
    
    import helper.person
    
    def _return_ids(self):
        import helper as funclevel_module
        import helper.person as funclevel_class
        return (id(funclevel_module),id(funclevel_class))
    
    def test_uniqueness(self):
        
        global_module_id = id(helper)
        global_class_id = id(helper.person)
        
        classlevel_module_id = id(TestImport.helper)
        classlevel_class_id = id(TestImport.helper.person)
        
        self.assertEqual(global_module_id,classlevel_module_id)
        self.assertEqual(global_class_id,classlevel_class_id)
        
        # -------------- function level
        ids = self._return_ids()
        self.assertEqual(ids[0],global_module_id)
        self.assertEqual(ids[1],global_class_id)
        
    def test_same_objects(self):
        # when python load modules, it will first look for that module in "sys.modules"
        # only load that module (including the compiling process) when it cannot find that module in "sys.modules"
        # if that module has already been loaded, which means it can be found in "sys.modules"
        # then "import" just bind that "module object" and "class object" to its local namespace
        # so the same module (and all classes and functions in that module), although imported by different module
        # multiple times, but that is just different local names, but bound to the same background object
        self.assertTrue(helper.person.Person is team.person.Person)
        
        # when module loaded, all the statements in that module will be executed (class statement and function statement)
        # when class statement is executed, its function and class-level member variables are executed, and are bound
        # to that class's local namespace
        # since a specific module is loaded only once, so these statement are also executed only once
        self.assertTrue(helper.person.Person.RACE_YELLOW is team.person.Person.RACE_YELLOW)
        self.assertTrue(helper.person.Person.RACE_WHITE is team.person.Person.RACE_WHITE)
        self.assertTrue(helper.person.Person.RACE_BLACK is team.person.Person.RACE_BLACK)
        
        # there is only one copy of class object in each PVM, (that is, each python process)
        # imported by different modules, just create different names in multiple namespace
        # but all these names (names in different namespace) refer to the same object
        newvalue = 111
        helper.person.Person.RACE_BLACK = newvalue # change by a name, affect others
        self.assertEqual(newvalue,team.person.Person.RACE_BLACK)
        
    def test_from_import_immutable(self):
        """
        from xxx import zzz, then zzz are just local names which references the same object in xxx
        if "=" is used to redirect zzz to other objects, then changes made by zzz will not affect that original object in xxx
        """
        global immutable_variable # necessary, indicating this is not local name
        self.assertEqual("cheka",immutable_variable)
        immutable_variable = 100 # redirect to other, detached from original object
        self.assertEqual("cheka",importee.get_immutable_variable())
        
        # must chaning the original one
        importee.immutable_variable = 128
        self.assertEqual(128,importee.get_immutable_variable())
        self.assertEqual(100,immutable_variable) # totally detached
        
    def test_from_import_mutable(self):
        # before changing
        self.assertEqual(0,len(mutable_variable))
        self.assertEqual([],importee.get_mutable_variable())
        
        # after changing
        mutable_variable.append(99)
        self.assertTrue(mutable_variable is importee.mutable_variable) # still referring to the same object
        self.assertEqual([99],importee.get_mutable_variable())

if __name__ == "__main__":
    unittest.main()
        
        